/**
 *Copyright (c) 2024 watereyes
 * safetymanagement is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan
 * PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package com.koron.system.aop;


import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.alibaba.fastjson.JSONObject;
import com.koron.common.bean.DepartmentTreeBean;
import com.koron.common.bean.DeptBean;
import com.koron.common.bean.SecretBean;
import com.koron.common.bean.StaffBean;
import com.koron.common.generator.DefaultIdGenerator;
import com.koron.common.stub.Port;
import com.koron.common.web.Util;
import com.koron.system.annotation.Log;
import com.koron.system.domain.OperationLog;
import com.koron.system.mapper.OperationLogMapper;
import com.koron.system.util.IpUtils;
import com.koron.system.util.LocationUtils;
import com.koron.system.util.ServletUtils;


import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.koron.ebs.mybatis.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.swan.bean.MessageBean;


@Aspect
@Component
public class LogAspect {

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    @Value("${app.appid}")
    private String appid;

    @Value("${app.secret}")
    private String secret;

    private final String PREFIX = "operation_log";

    private final Object lock = new Object();

    private Set<String> nameCache = new HashSet<>();

    private ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

    @Autowired
    private Util util;

    // 配置织入点
    @Pointcut("@annotation(com.koron.system.annotation.Log)")
    public void logPointCut() {
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()")
    public void doAfterReturning(JoinPoint joinPoint) {
        handleLog(joinPoint, null);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e);
    }

    private void interpolate(final OperationLog operationLog) {
        singleThreadExecutor.execute(
                new Runnable() {
                    @Override
                    public void run() {
                        try (SessionFactory factory = new SessionFactory()) {
                            String tableName = getTableName();
                            OperationLogMapper mapper = factory.getMapper(OperationLogMapper.class);
                            synchronized (lock) {
                                if (!nameCache.contains(tableName) && mapper.existTable(tableName) < 1) {
                                    mapper.createTable(tableName);
                                    nameCache.add(tableName);
                                }
                            }
                            mapper.insert(tableName, operationLog);
                        }
                    }
                }
        );
    }

    /**
     * 获取操作日志表名
     * operation_log + 年份
     *
     * @return
     */
    private String getTableName() {
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        StringBuilder sb = new StringBuilder(PREFIX);
        sb.append("_").append(year);
        return sb.toString();
    }


    protected void handleLog(final JoinPoint joinPoint, final Exception e) {
        try {
            // 获得注解
            Log controllerLog = getAnnotationLog(joinPoint);
            if (controllerLog == null) {
                return;
            }
            // 获取当前的用户
            StaffBean currentUser = ServletUtils.getSigninUser();
            OperationLog operationLog = new OperationLog();
            operationLog.setId(DefaultIdGenerator.getInstance().generateLongId());
            operationLog.setStatus(0);
            operationLog.setOperationIP(IpUtils.getIpAddr(ServletUtils.getRequest()));
            operationLog.setOperationLocation(LocationUtils.getRealAddressByIP(operationLog.getOperationIP()));
            operationLog.setOperationUrl(ServletUtils.getRequest().getRequestURI());
            if (currentUser != null) {
                operationLog.setOperatorName(currentUser.getName());
                MessageBean<List> messageBean = getPort().dept(currentUser.getCode());
                if (messageBean.getData() != null && !messageBean.getData().isEmpty()) {
                    DeptBean deptBean = (DeptBean) messageBean.getData().get(0);
                    operationLog.setDepartmentName(deptBean.getName());
                    if(currentUser.getCode() != null && !currentUser.getCode().isEmpty()) {
                        //查询该用户所处公司名称
//                          OrgBean org = factory.getMapper(OrgMapper.class).getOrg(deptBean.getOrgCode());
                            DepartmentTreeBean corp = (DepartmentTreeBean) util.corp(currentUser.getCode()).getData();
                            operationLog.setOrgName(corp.getName());

                    }
                }
            }
            if (e != null) {
                operationLog.setStatus(1);
                operationLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operationLog.setMethod(className + "." + methodName + "()");
            // 处理设置注解上的参数
            getControllerMethodDescription(controllerLog, operationLog);
            setRequestParams(operationLog);
            operationLog.setOperationTime(new Date());
            interpolate(operationLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("异常信息:{}", exp.getMessage());
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log          日志
     * @param operationLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(Log log, OperationLog operationLog) throws Exception {
        operationLog.setTitle(log.title());
        operationLog.setBusinessModule(log.businessModule());
        operationLog.setBusinessManipulation(log.businessManipulation().ordinal());
    }


    private void setRequestParams(OperationLog operationLog) throws Exception {
        Map<String, String[]> map = ServletUtils.getRequest().getParameterMap();
        String params = JSONObject.toJSONString(map);
        operationLog.setParams(StringUtils.substring(params, 0, 2000));
    }

    /**
     * 是否存在注解，如果存在就获取
     */
    private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(Log.class);
        }
        return null;
    }

    private Port getPort() {
        SecretBean secretBean = new SecretBean();
        secretBean.setAppid(appid);
        secretBean.setSecret(secret);
        return new Port(secretBean);
    }


}
